import OrderedMap from "orderedmap";
import { Mark } from "./mark.ts";
import { ContentMatch } from "./content.ts";
import { Fragment } from "./fragment.ts";
import { Node } from "./node.ts";

export type Attrs = { [attr: string]: any };

function defaultAttrs(attrs: { [name: string]: Attribute }) {
  const defaults = Object.create(null);
  for (const attrName in attrs) {
    const attr = attrs[attrName];
    if (!attr.hasDefault) return null;
    defaults[attrName] = attr.default;
  }
  return defaults;
}

export type FragmentFn = (content: Fragment | Node | Node[] | null) => Fragment;
export type FragmentVerifyFn = (content: Fragment) => boolean;
export interface FragmentFactory {
  [name: string]: FragmentFn;
}
export interface FragmentVerifyFactory {
  [name: string]: FragmentVerifyFn;
}

export interface AttributeSpec {
  default?: any;
}

class Attribute {
  hasDefault: boolean;
  default: any;

  constructor(options: AttributeSpec) {
    this.hasDefault = Object.prototype.hasOwnProperty.call(options, "default");
    this.default = options.default;
  }

  get isRequired(): boolean {
    return !this.hasDefault;
  }
}

export interface SchemaSpec<
  Nodes extends string = any,
  Marks extends string = any,
> {
  nodes: { [name in Nodes]: NodeSpec } | OrderedMap<NodeSpec>;

  marks?: { [name in Marks]: MarkSpec } | OrderedMap<MarkSpec>;
  fragmentFactory?: { [name in string]: FragmentFn } | OrderedMap<FragmentFn>;
  fragmentVerifyFactory?:
    | { [name in string]: FragmentVerifyFn }
    | OrderedMap<FragmentVerifyFn>;
  topNode?: string;
}

function defaultFragmentFactory(): (
  content?: Fragment | Node | Node[] | null,
) => Fragment {
  return (content?: Fragment | Node | Node[] | null) => {
    return Fragment.from(content);
  };
}
/**
 * Schema 定义,包含 节点类型定义 和 标记类型定义 fragment工厂 和顶级节点
 */
export class Schema<Nodes extends string = any, Marks extends string = any> {
  spec: {
    nodes: OrderedMap<NodeSpec>;
    marks: OrderedMap<MarkSpec>;
    fragmentFactory: OrderedMap<FragmentFactory>;
    fragmentVerifyFactory: OrderedMap<FragmentVerifyFactory>;
    topNode?: string;
  };
  topNodeType: NodeType;
  cached: { [key: string]: any } = Object.create(null);
  nodes: { [name in Nodes]: NodeType } & { [key: string]: NodeType };
  fragmentFactory: { [name in Nodes]: FragmentFn } & {
    [key: string]: FragmentFn;
  };
  fragmentVerifyFactory: { [name in Nodes]: FragmentVerifyFn } & {
    [key: string]: FragmentVerifyFn;
  };
  /// A map from mark names to mark type objects.
  marks: { [name in Marks]: MarkType } & { [key: string]: MarkType };

  constructor(spec: SchemaSpec<Nodes, Marks>) {
    const instanceSpec = this.spec = {} as any;
    for (let prop in spec) instanceSpec[prop] = (spec as any)[prop];
    instanceSpec.nodes = OrderedMap.from(spec.nodes);
    instanceSpec.marks = OrderedMap.from(spec.marks || {});

    instanceSpec.fragmentVerifyFactory = OrderedMap.from(
      spec.fragmentVerifyFactory || {},
    );
    instanceSpec.fragmentFactory = OrderedMap.from(spec.fragmentFactory || {});

    const { factorys, vfactorys } = Schema.compile(
      this.spec.fragmentFactory,
      this.spec.fragmentVerifyFactory,
    );
    this.fragmentFactory = factorys;
    this.fragmentVerifyFactory = vfactorys;
    this.nodes = NodeType.compile(this.spec.nodes, this);
    this.marks = MarkType.compile(this.spec.marks, this);

    const contentExprCache = Object.create(null);
    for (let prop in this.nodes) {
      if (prop in this.marks) {
        throw new RangeError(prop + " 不能既是节点又是标记");
      }
      const type = this.nodes[prop],
        contentExpr = type.spec.content || "",
        markExpr = type.spec.marks;
      type.contentMatch = contentExprCache[contentExpr] ||
        (contentExprCache[contentExpr] = ContentMatch.parse(
          contentExpr,
          this.nodes,
        ));
      type.markSet = markExpr == "_"
        ? null
        : markExpr
        ? gatherMarks(this, markExpr.split(" "))
        : null;
    }

    this.topNodeType = this.nodes[this.spec.topNode || "doc"];
    //公共缓存
    this.cached.wrappings = Object.create(null);
  }

  /**
   * @param factorys
   */
  static compile(
    factorys: OrderedMap<FragmentFactory>,
    vfactorys: OrderedMap<FragmentVerifyFactory>,
  ): { factorys: any; vfactorys: any } {
    const result = Object.create(null);
    factorys.forEach((name, spec) => {
      return result[name] = spec;
    });
    const resultv = Object.create(null);
    vfactorys.forEach((name, spec) => {
      return resultv[name] = spec;
    });
    return { factorys: result, vfactorys: resultv };
  }

  /**
   * 获取一个节点类型
   * @param name
   */
  nodeType(name: string): NodeType {
    const found = this.nodes[name];
    if (!found) throw new RangeError("未知的节点类型: " + name);
    return found;
  }

  /**
   * 创建一个节点
   * @param id
   * @param type
   * @param attrs
   * @param content
   * @param marks
   */
  node(
    id: string,
    type: string | NodeType,
    attrs: Attrs | null = null,
    content?: Fragment | Node | Node[],
    marks?: Mark[],
  ): Node {
    if (typeof type == "string") {
      type = this.nodeType(type);
    } else if (!(type instanceof NodeType)) {
      throw new RangeError("无效的节点类型: " + type);
    } else if (type.schema != this) {
      throw new RangeError("使用来自不同架构的节点类型 (" + type.name + ")");
    }

    return (type as NodeType).createChecked(id, attrs, content || null, marks);
  }

  mark(type: string | MarkType, attrs?: Attrs | null): Mark {
    if (typeof type == "string") type = this.marks[type];
    return (type as MarkType).create(attrs);
  }
  nodeFromJSON(json: any): Node {
    return Node.fromJSON(this, json);
  }
  markFromJSON(json: any): Mark {
    return Mark.fromJSON(this, json);
  }
}

function initAttrs(attrs?: { [name: string]: AttributeSpec }) {
  const result: { [name: string]: Attribute } = Object.create(null);
  if (attrs) {
    for (const name in attrs) {
      result[name] = new Attribute(attrs[name]);
    }
  }
  return result;
}

/**
 * 节点的类型定义
 */
export class NodeType {
  /// @internal
  groups: string[];
  /// @internal
  attrs: { [name: string]: Attribute };
  /// @internal
  defaultAttrs: Attrs;
  contentMatch!: ContentMatch;
  //当前节点类型所允许的mark类型
  markSet: MarkType[] | null = null;
  name: string;
  schema: Schema;
  spec: NodeSpec;
  /// @internal
  constructor(
    /// 节点类型在此模式中的名称。
    name: string,
    /// 所属的模式
    schema: Schema,
    spec: NodeSpec,
  ) {
    this.name = name;
    this.schema = schema;
    this.spec = spec;
    this.groups = spec.group ? spec.group.split(" ") : [];
    this.attrs = initAttrs(spec.attrs);
    this.defaultAttrs = defaultAttrs(this.attrs);
    (this as any).contentMatch = null;
  }

  /**
   * 检查是否包含必填属性
   */
  hasRequiredAttrs(): boolean {
    for (let n in this.attrs) if (this.attrs[n].isRequired) return true;
    return false;
  }

  /**
   * 创建一个节点
   * @param id 唯一标识
   * @param attrs 属性
   * @param fragment 子节点
   * @param marks 标记
   */
  create(
    id: string,
    attrs: Attrs | null = null,
    fragment: Fragment | Node | Node[] | null,
    marks?: Mark[],
  ): Node {
    fragment =
      (this.schema.fragmentFactory[this.name] || defaultFragmentFactory())(
        fragment,
      );
    return new Node(
      id,
      this,
      this.computeAttrs(attrs),
      fragment,
      Mark.setFrom(marks),
    );
  }

  /**
   * 创建一个节点，并检查其内容是否合法
   * @param id 唯一标识
   * @param attrs 属性
   * @param fragment 子节点
   * @param marks 标记
   */
  createChecked(
    id: string,
    attrs: Attrs | null = null,
    fragment: Fragment | Node | Node[] | null,
    marks?: Mark[],
  ): Node {
    fragment =
      (this.schema.fragmentFactory[this.name] || defaultFragmentFactory())(
        fragment,
      );
    this.checkContent(fragment);
    return new Node(
      id,
      this,
      this.computeAttrs(attrs),
      fragment,
      Mark.setFrom(marks),
    );
  }

  checkContent(content: Fragment) {
    const verify = this.schema.fragmentVerifyFactory[this.name];
    if ((verify && !verify(content)) || !this.validContent(content)) {
      throw new RangeError(`${this.name} 子节点 类型 或 mark类型 无效`);
    }
  }

  /**
   * 检查节点内容是否合法
   * @param fragment
   */
  validContent(fragment: Fragment): boolean {
    const result = this.contentMatch.matchFragment(fragment);
    if (!result || !result.validEnd) return false;
    for (let i = 0; i < fragment.childCount; i++) {
      if (!this.allowsMarks(fragment.child(i).marks)) return false;
    }
    return true;
  }

  /**
   * 检查mark是否合法
   * @param marks
   */
  allowsMarks(marks: Mark[]): boolean {
    if (this.markSet == null) return true;
    for (let i = 0; i < marks.length; i++) {
      if (!this.allowsMarkType(marks[i].type)) return false;
    }
    return true;
  }

  /**
   * 检查mark类型是否合法
   * @param markType
   */
  allowsMarkType(markType: MarkType): boolean {
    return this.markSet == null || this.markSet.indexOf(markType) > -1;
  }

  computeAttrs(attrs: Attrs | null): Attrs {
    if (!attrs && this.defaultAttrs) return this.defaultAttrs;
    else return computeAttrs(this.attrs, attrs);
  }

  static compile<Nodes extends string>(
    nodes: OrderedMap<NodeSpec>,
    schema: Schema<Nodes>,
  ): { [name in Nodes]: NodeType } {
    const result = Object.create(null);
    nodes.forEach((name, spec) => {
      return result[name] = new NodeType(name, schema, spec);
    });
    const topType = schema.spec.topNode || "doc";
    if (!result[topType]) {
      throw new RangeError("缺少顶级节点类型 ('" + topType + "')");
    }
    return result;
  }
}

/**
 * 节点的类型约束
 */
export interface NodeSpec {
  content?: string;

  marks?: string;

  group?: string;

  attrs?: { [name: string]: AttributeSpec };

  [key: string]: any;
}

function computeAttrs(
  attrs: { [name: string]: Attribute },
  value: Attrs | null,
) {
  const built = Object.create(null);
  for (const name in attrs) {
    let given = value && value[name];
    if (given === undefined) {
      const attr = attrs[name];
      if (attr.hasDefault) given = attr.default;
      else throw new RangeError("没有为属性提供值 " + name);
    }
    built[name] = given;
  }
  return built;
}

/**
 * 标记的类型定义
 */
export class MarkType {
  attrs: { [name: string]: Attribute };
  excluded!: MarkType[];
  instance: Mark | null;
  name: string;
  rank: number;
  schema: Schema;
  spec: MarkSpec;

  constructor(
    name: string,
    rank: number,
    schema: Schema,
    spec: MarkSpec,
  ) {
    this.name = name;
    this.rank = rank;
    this.schema = schema;
    this.spec = spec;
    this.attrs = initAttrs(spec.attrs);
    (this as any).excluded = null;
    const defaults = defaultAttrs(this.attrs);
    this.instance = defaults ? new Mark(this, defaults) : null;
  }

  /**
   * 创建一个标记
   * @param attrs
   */
  create(attrs: Attrs | null = null): Mark {
    if (!attrs && this.instance) return this.instance;
    return new Mark(this, computeAttrs(this.attrs, attrs));
  }

  /**
   * 检查是否与另一个标记不冲突
   * @param other
   */
  excludes(other: MarkType): boolean {
    return this.excluded.indexOf(other) > -1;
  }

  static compile(marks: OrderedMap<MarkSpec>, schema: Schema): any {
    const result = Object.create(null);
    let rank = 0;
    marks.forEach((name, spec) =>
      result[name] = new MarkType(name, rank++, schema, spec)
    );
    return result;
  }
}

/**
 * 标记的类型约束
 */
export interface MarkSpec {
  attrs?: { [name: string]: AttributeSpec };

  excludes?: string;

  group?: string;

  spanning?: boolean;

  [key: string]: any;
}

function gatherMarks(schema: Schema, marks: string[]) {
  const found: MarkType[] = [];
  for (let i = 0; i < marks.length; i++) {
    const name = marks[i], mark = schema.marks[name];
    let ok = mark;
    if (mark) {
      found.push(mark);
    } else {
      for (const prop in schema.marks) {
        const mark = schema.marks[prop];
        if (
          name == "_" ||
          (mark.spec.group && mark.spec.group.split(" ").indexOf(name) > -1)
        ) {
          found.push(ok = mark);
        }
      }
    }
    if (!ok) throw new SyntaxError("未知的标记类型: '" + marks[i] + "'");
  }
  return found;
}
